#include "XWindow.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "XIAC.h"
#include "String.h"
#include "ctype.h"
#include "System.h"
#include "OrdColl.h"
#include "Timer.h"
#include "XWinSystem.h"
#include "ET_stdio.h"

//---- XClient -----------------------------------------------------------------

class XClient : public Object {
public:
	XWindow win;
	char *name;
	bool died;
public:
	XClient(const char *name, XWindow w);
	~XClient();
	void Reset()
		{ died= TRUE; }
};

XClient::XClient(const char *nm, XWindow w)
{
	name= strsave(nm);
	win= w;
	died= FALSE;
}

XClient::~XClient()
{
	SafeDelete(name);
}

//---- XIAC --------------------------------------------------------------------

XIAC::XIAC(IAC *s, XDisplay *d, XWindow w) : IACImpl(s)
{
	win= w;
	freewin= FALSE;
	disp= d;
	clients= new OrdCollection;
}

XIAC::~XIAC()
{
	if (freewin && win) {
		XDestroyWindow(disp, win);
		win= 0;
	}
	if (clients) {
		clients->FreeAll();
		SafeDelete(clients);
	}
}

int XIAC::Open(const char *nm)
{
	if (disp == 0) {
		disp= XOpenDisplay(0);
		if (disp == 0) {
			fprintf(stderr, "can't open display\n");
			return -1;
		}
	}

	int screen= XDefaultScreen(disp);
	root= XRootWindow(disp, screen);
	XSelectInput(disp, root, PropertyChangeMask);

	registry= XInternAtom(disp, "ET++Registry", False);
	comm= XInternAtom(disp, "ET++Comm", False);
	et= XInternAtom(disp, "ET++", False);

	if (win == 0) {
		XSetWindowAttributes attr;
		attr.override_redirect= True;
		win= XCreateWindow(disp, root, 0, 0, 1, 1, 0, XDefaultDepth(disp, screen),
			InputOutput, (Visual*) CopyFromParent, CWOverrideRedirect, &attr);
		freewin= TRUE;
	}
	XSelectInput(disp, win, PropertyChangeMask);

	name= strsave(nm);

	XChangeProperty(disp, win, et, XA_STRING, 8, PropModeReplace,
								(u_char*) name, (u_long)(strlen(name)+1));
	XChangeProperty(disp, root, registry, XA_STRING, 8, PropModeAppend, 0, 0);

	XFlush(disp);

	VerifyClients();

	return win;
}

int XIAC::Close(const char*)
{
	XDeleteProperty(disp, win, et);
	XChangeProperty(disp, root, registry, XA_STRING, 8, PropModeAppend, 0, 0);
	return 0;
}

int XIAC::Lookup(const char *nm)
{
	if (goterror) {
		VerifyClients();
		goterror= FALSE;
	}
	XWindow w= FindWin(nm);
	if (w)
		return w;

	// not found -> verify
	VerifyClients();
	return FindWin(nm);
}

int XIAC::LookupName(const char *nm, bool start)
{
	int w= Lookup(nm);
	if (w)
		return w;

	// not found -> start tool
	if (start) {
		char *t= form("%s/tools/%s &", gEtDir, nm);
		fprintf(stderr, "Starting Tool: %s\n", t);
		int rc= gSystem->Exec(t);
		if (rc < 0)
			perror("XIAC::LookupName");
		else {
			Timer th(30000); // wait for 30 seconds
			while (w == 0 && !th.WasTimeout()) {
				gSystem->InnerLoop();
				w= Lookup(nm);
			}
		}
	}
	return w;
}

int XIAC::SendReply(int to, int serial, int code, const char *buf, int len)
{
	char *property= new char[len + 30];
	sprintf(property, "R %x %x %s", serial, code, buf);
	//VerifyClients();
	XChangeProperty(disp, to, comm, XA_STRING, 8, PropModeAppend,
							(u_char*)property, (u_long)(strlen(property)+1));
	XFlush(disp);
	delete property;
	return 0;
}

int XIAC::Broadcast(int serial, const char *buf, int len)
{
	u_char *property= new u_char[len + 30];
	sprintf((char*)property, "C %x %x %s", serial, win, buf);
	u_long l= strlen((char*)property)+1;

	register XClient *xc;
	Iter next(clients);
	while (xc= (XClient*) next())
		if (xc->win != win)
			XChangeProperty(disp, xc->win, comm, XA_STRING, 8,
													PropModeAppend, property, l);
	XFlush(disp);
	delete property;
	return 0;
}

int XIAC::Poll(int serial, const char *buf, int len)
{
	u_char *property= new u_char[len + 30];
	sprintf((char*)property, "C %x %x %s", serial, win, buf);
	u_long l= strlen((char*)property)+1;

	register XClient *xc;
	Iter next(clients);
	while (xc= (XClient*) next())
		if (xc->win != win)
			XChangeProperty(disp, xc->win, comm, XA_STRING, 8,
													PropModeAppend, property, l);
	XFlush(disp);
	delete property;
	return 0;
}

int XIAC::SendTo(int to, int serial, const char *buf, int len)
{
	u_char *property= new u_char[len + 30];
	sprintf((char*)property, "C %x %x %s", serial, win, buf);
	u_long l= strlen((char*)property)+1;

	XChangeProperty(disp, to, comm, XA_STRING, 8, PropModeAppend, property, l);
	XFlush(disp);
	delete property;
	return 0;
}

//---- internals ---------------------------------------------------------------

XWindow XIAC::FindWin(const char *nm)
{
	Iter next(clients);
	register XClient *xc;

	while (xc= (XClient*) next())
		if (strcmp(xc->name, nm) == 0)
			return xc->win;
	return 0;
}

void XIAC::VerifyClients()
{
	u_int nchild;
	XWindow w, *children= 0;

	clients->ForEach(XClient,Reset)();

	//XGrabServer(disp);
	if (XQueryTree(disp, root, &w, &w, &children, &nchild) != 0 && children) {

		for (int i= 0; i < nchild; i++) {
			char *v;
			if (v= GetProperty(children[i], et, False, 0)) {
				UpdateClient(v, children[i]);
				XFree(v);
			}
		}
		
		XFree((char*)children);
	}
	XFlush(disp);
	//XUngrabServer(disp);

	TestClients();
}

void XIAC::UpdateClient(const char *nm, XWindow w)
{
	Iter next(clients);
	register XClient *xc;

	while (xc= (XClient*) next()) {
		if (xc->win == w && strcmp(xc->name, nm) == 0) {
			xc->died= FALSE;
			return;
		}
	}
	//Dispatch(0, serial, p, strlen(p));
	fprintf(stderr, "New client: %s\n", nm);
	clients->Add(new XClient(nm, w));
}

void XIAC::TestClients()
{
	Iter next(clients);
	register XClient *xc;

	while (xc= (XClient*) next()) {
		if (xc->died) {
			fprintf(stderr, "Client died: %s\n", xc->name);
			clients->RemovePtr(xc);
		}
	}
}

void XIAC::HandleError(XErrorEvent *ev)
{
	fprintf(stderr, "XIAC::HandleError\n");
	goterror= TRUE;
}

void XIAC::ProcessRequest(XEvent*)
{
	register char *p;
	char *propInfo;
	u_long len;

	// Read the comm property and delete it
	propInfo= GetProperty(win, comm, True, &len);

	for (p= propInfo; (p-propInfo) < len; ) {
		char *end;
		int cc, n2, serial;

		//fprintf(stderr, "%s\n", p);
		cc= *p++;
		serial= strtol(p, &end, 16);
		if (end == p)
			goto nextRecord;
		p= end;
		if (*p != ' ')
			goto nextRecord;
		p++;
		n2= strtol(p, &end, 16);
		if (end == p)
			goto nextRecord;
		p= end;
		if (*p != ' ')
			goto nextRecord;
		p++;
		
		SendUp(cc == 'R', n2, serial, p);
		
	nextRecord:
		while (*p++)
			;
	}
	XFree(propInfo);
}

bool XIAC::HandleEvent(XEvent &ev)
{
	if (ev.xproperty.atom == comm) {
		if (ev.xproperty.state != PropertyDelete) {
			ProcessRequest(&ev);
			return TRUE;
		}
		return FALSE;
	}
	if (ev.xproperty.atom == registry) {
		VerifyClients();
		return TRUE;
	}
	return FALSE;
}

char *XIAC::GetProperty(XWindow w, Atom &atom, int del, u_long *len)
{
	return ((XWinSystem*)gWindowSystem)->GetProperty(w, atom, del, len);
}

